import axios from 'axios'
import type {
  AxiosInstance,
  AxiosResponse,
  Method,
  AxiosError,
  InternalAxiosRequestConfig
} from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import { localStore } from '@/utils/storage'
import { useRouter } from 'vue-router'
import type { RequestOptions, ResultBean } from './type'

const router = useRouter()
/** loading实例，是一个单例对象 */
let loadingInstance: any = null
/** 使用计数器对loading进行控制 */
let requestCount: number = 0

type RequestConfig = InternalAxiosRequestConfig & RequestOptions

/**
 * 请求类
 * 封装axios请求，返回一致的结果，并处理部分通用情况
 */
export default class Http {
  private instance: AxiosInstance

  constructor(baseURL: string) {
    this.instance = axios.create({
      baseURL,
      timeout: 60000
    })
    requestCount = 0

    this.initInterceptors()
  }

  private initInterceptors() {
    // 配置请求拦截器
    this.instance.interceptors.request.use(
      (config: RequestConfig) => {
        if (config.joinTime) {
          config.params = { ...(config.params || {}), t: new Date().getTime() }
        }
        if (config.withToken) {
          const token = localStore.get('token')
          config.headers['token'] = token
        }
        if (config.showLoading !== false) {
          requestCount++
          loadingInstance = ElLoading.service({
            lock: true,
            text: 'Loading',
            background: 'rgba(0, 0, 0, 0.7)'
          })
        }
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )

    // 配置响应拦截器
    this.instance.interceptors.response.use(
      (response: AxiosResponse) => response,
      (error: AxiosError<any>) => {
        let jumpTo = ''
        const response = error.response

        if (response) {
          // 响应状态码
          switch (response.status) {
            case 401:
              error.message = '您的登录信息已失效！'
              jumpTo = '/error/401'
              break
            case 403:
              error.message = '您没有权限进行该操作！'
              jumpTo = '/error/403'
              break
            case 404:
              error.message = '未找到相关服务！'
              jumpTo = '/error/404'
              break
            case 400:
            case 500:
            default:
              error.message = response.data?.msg || '请求失败'
          }
        }
        // @ts-ignore
        if (error.config.showError !== false && !jumpTo) {
          ElMessage.error({ grouping: true, message: error.message })
        }

        if (jumpTo) {
          router.push(jumpTo)
        }
        return Promise.reject(error)
      }
    )
  }

  /**
   * 发送请求
   * 为了使返回数据统一，这里统一返回 ResultBean 对象
   * @param method 请求方法，如 'get'、'post' 等
   * @param url 请求地址
   * @param data 请求参数
   * @param config 请求配置
   * @returns 返回请求结果
   */
  async request<T, R>(
    method: Method,
    url: string,
    data?: T,
    config?: RequestOptions
  ): Promise<ResultBean<R>> {
    let response = null
    let result: ResultBean<R> = {
      data: null,
      message: '',
      isSuccess: false
    }

    try {
      if (['post', 'put', 'patch'].includes(method.toLowerCase())) {
        response = await this.instance.request({
          method,
          url,
          data,
          ...config
        })
      } else {
        response = await this.instance.request({
          method,
          url,
          params: data,
          ...config
        })
      }

      result = {
        data: response.data,
        message: response.data?.message || '请求成功',
        isSuccess: true
      }
    } catch (error: any) {
      result = {
        data: null,
        message: error?.message || '请求成功',
        isSuccess: false
      }
    }
    if (config?.showLoading !== false) {
      requestCount--
      // 请求全部完成后关闭 loading
      if (requestCount === 0 && loadingInstance) {
        loadingInstance.close()
      }
    }
    return Promise.resolve(result)
  }

  // 通用GET请求方法
  get<T, R>(url: string, params?: T, config?: RequestOptions): Promise<ResultBean<R>> {
    return this.request<T, R>('get', url, params, config)
  }

  // 通用POST请求方法
  post<T, R>(url: string, data: T, config?: RequestOptions): Promise<ResultBean<R>> {
    return this.request<T, R>('post', url, data, config)
  }

  put<T, R>(url: string, data: T, config?: RequestOptions): Promise<ResultBean<R>> {
    return this.request<T, R>('put', url, data, config)
  }

  patch<T, R>(url: string, data: T, config?: RequestOptions): Promise<ResultBean<R>> {
    return this.request<T, R>('patch', url, data, config)
  }

  delete<T, R>(url: string, params?: T, config?: RequestOptions): Promise<ResultBean<R>> {
    return this.request<T, R>('delete', url, params, config)
  }
}
